﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace StyleMVVM.Utilities
{
	public class GenericWeakAction<T>
	{
		private Action<object, T> executeAction;
		private WeakReference reference;
		private MethodInfo methInfo;

		public GenericWeakAction(Action<T> action) : this(action, false)
		{
		}

		/// <summary>
		/// Use this method if you want to create a compiled linq accessor. 
		/// Its a slight penalty up front but is much faster if you call it alot.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="compiledAccessor"></param>
		public GenericWeakAction(Action<T> action, bool compiledAccessor)
		{
			if (action.Target != null)
			{
				MethodInfo tempInfo = action.GetMethodInfo();

				init(action.Target, tempInfo, compiledAccessor);
			}
			else
			{
				throw new ArgumentNullException("Action.Target can not be null");
			}
		}

		public GenericWeakAction(object target, MethodInfo method, bool compiledAccessor)
		{
			init(target, method, compiledAccessor);
		}

		protected void init(object target, MethodInfo method, bool compiledAccessor)
		{
			reference = new WeakReference(target);

			methInfo = method;

			if (compiledAccessor)
			{
				ParameterExpression targetParam = Expression.Parameter(typeof(object), "target");
				ParameterExpression actionParam =
					Expression.Parameter(typeof(T), "actionParam");

				UnaryExpression castTarget =
					Expression.ConvertChecked(targetParam, target.GetType());

				Expression call = Expression.Call(castTarget,
				                                  methInfo,
				                                  new Expression[] { actionParam });

				executeAction =
					Expression.Lambda<Action<object, T>>(call, targetParam, actionParam).Compile();
			}
		}

		public object Target
		{
			get { return reference.Target; }
		}

		public MethodInfo MethodInfo
		{
			get { return methInfo; }
		}

		public bool IsAlive
		{
			get { return reference.IsAlive; }
		}

		public bool Execute(T value)
		{
			object target = reference.Target;

			if (target != null)
			{
				if (executeAction != null)
				{
					executeAction(target, value);
				}
				else
				{
					methInfo.Invoke(target, new object[] { value });
				}

				return true;
			}

			return false;
		}
	}

	public class GenericWeakAction<T, T2>
	{
		private Action<object, T, T2> executeAction;
		private WeakReference reference;
		private MethodInfo methInfo;

		public GenericWeakAction(Action<T, T2> action)
			: this(action, false)
		{
		}

		/// <summary>
		/// Use this method if you want to create a compiled linq accessor. 
		/// Its a slight penalty up front but is much faster if you call it alot.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="compiledAccessor"></param>
		public GenericWeakAction(Action<T, T2> action, bool compiledAccessor)
		{
			if (action.Target != null)
			{
				dynamic tempD = action;

				MethodInfo tempInfo = tempD.Method;

				init(action.Target, tempInfo, compiledAccessor);
			}
			else
			{
				throw new ArgumentNullException("Action.Target can not be null");
			}
		}

		/// <summary>
		/// Use this method if you want to create a compiled linq accessor. 
		/// Its a slight penalty up front but is much faster if you call it alot.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="compiledAccessor"></param>
		public GenericWeakAction(Delegate action)
			: this(action, false)
		{
		}

		/// <summary>
		/// Use this method if you want to create a compiled linq accessor. 
		/// Its a slight penalty up front but is much faster if you call it alot.
		/// </summary>
		/// <param name="action"></param>
		/// <param name="compiledAccessor"></param>
		public GenericWeakAction(Delegate action, bool compiledAccessor)
		{
			if (action.Target != null)
			{
				dynamic tempD = action;

				MethodInfo tempInfo = tempD.Method;

				init(action.Target, tempInfo, compiledAccessor);
			}
			else
			{
				throw new ArgumentNullException("Action.Target can not be null");
			}
		}

		public GenericWeakAction(object target, MethodInfo method, bool compiledAccessor)
		{
			init(target, method, compiledAccessor);
		}

		protected void init(object target, MethodInfo method, bool compiledAccessor)
		{
			reference = new WeakReference(target);

			methInfo = method;

			if (compiledAccessor)
			{
				ParameterExpression targetParam = Expression.Parameter(typeof(object), "target");
				ParameterExpression actionParam =
					Expression.Parameter(typeof(T), "actionParam");
				ParameterExpression actionParam2 =
					Expression.Parameter(typeof(T2), "actionParam");

				UnaryExpression castTarget =
					Expression.ConvertChecked(targetParam, target.GetType());

				Expression call = Expression.Call(castTarget,
				                                  methInfo,
				                                  new Expression[] { actionParam, actionParam2 });

				executeAction =
					Expression.Lambda<Action<object, T, T2>>(call, targetParam, actionParam, actionParam2).Compile();
			}
		}

		public object Target
		{
			get { return reference.Target; }
		}

		public MethodInfo MethodInfo
		{
			get { return methInfo; }
		}

		public bool IsAlive
		{
			get { return reference.IsAlive; }
		}

		public bool Execute(T value, T2 value2)
		{
			object target = reference.Target;

			if (target != null)
			{
				if (executeAction != null)
				{
					executeAction(target, value, value2);
				}
				else
				{
					methInfo.Invoke(target, new object[] { value, value2 });
				}

				return true;
			}

			return false;
		}
	}
}